package ar.edu.itba.it.pdc.monitoring;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

import ar.edu.itba.it.pdc.configuration.Constants;
import ar.edu.itba.it.pdc.configuration.ProxyConfigurationLoader;
import ar.edu.itba.it.pdc.nio.TCPProtocol;


public class MonitoringProtocol implements TCPProtocol{

	private enum ProtocolState {
		NAME,
		VERSION,
		USER,
		PASS
	}
	
	private int bufSize;
	private ProxyMonitoring monitor;
	private double version;
	private String error;
	
	public MonitoringProtocol(int bufSize) {
		this.bufSize = bufSize;
		this.monitor = ProxyMonitoring.getInstance();
		this.version = Constants.MONITORVERSION;
	}

	@Override
	public void handleAccept(SelectionKey key) throws IOException {
		SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
		clntChan.configureBlocking(false); // Must be nonblocking to register
		// Register the selector with new channel for read and attach byte
		// buffer
		clntChan.register(key.selector(), SelectionKey.OP_READ, null);
	}

	@Override
	public void handleRead(SelectionKey key) throws IOException {
		SocketChannel clntChan = (SocketChannel) key.channel();
		
		List<ByteBuffer> bufs = new ArrayList<ByteBuffer>();
		bufs.add(ByteBuffer.allocate(bufSize));
		
		int i = 0;
		
		clntChan.read(bufs.get(i));
		
		while (!(new String(bufs.get(i).array()).contains("\n"))) { // Did the other end close?
			i++;
			bufs.add(ByteBuffer.allocate(bufSize));
			clntChan.read(bufs.get(i));
		}
		
		this.parseProtocol(bufs);
		key.interestOps(SelectionKey.OP_WRITE);
	}

	private void setError(String error) {
		this.error = error;
	}
	
	private void parseProtocol(List<ByteBuffer> bufs) {
		String content = "";
		ProtocolState protocolState = ProtocolState.NAME;
		for (ByteBuffer byteBuffer : bufs) {
			String buffer = new String(byteBuffer.array());
			char[] charBuffer = buffer.toCharArray();
			for (int i = 0; i < charBuffer.length; i++) {
				switch (protocolState) {
				case NAME:
					if (charBuffer[i] == '\n') {
						setError("Not enough parameters were provided for the monitoring protocol.\n" +
								"Remember: monitor version username password\n");
						return;
					}
					if (charBuffer[i] == ' ') {
						if (!"monitor".equals(content)) {
							setError("In order to use the monitor remember to use the command monitor.\n" +
								"Remember: monitor version username password\n");
							return;
						}
						protocolState = ProtocolState.VERSION;
						content = "";
					} else {
						content = content + charBuffer[i];
					}
					break;
				case VERSION:
					if (charBuffer[i] == '\n') {
						setError("Not enough parameters were provided for the monitoring protocol.\n" +
								"Remember: monitor version username password\n");
						return;
					}
					if (charBuffer[i] == ' ') {
						double dVersion = 0;
						try {
							dVersion = Double.parseDouble(content);
						} catch (Exception e) {
							setError("This version of the protocol is not supported.\n" +
									"Remember: monitor version username password\n");
							return;
						}
						if (dVersion > this.version) {
							setError("This version of the protocol is not supported.\n" +
									"Remember: monitor version username password\n");
							return;
						}
						protocolState = ProtocolState.USER;
						content = "";
					} else {
						content = content + charBuffer[i];
					}
					break;
				case USER:
					if (charBuffer[i] == '\n') {
						setError("Not enough parameters were provided for the monitoring protocol.\n" +
								"Remember: monitor version username password\n");
						return;
					}
					if (charBuffer[i] == ' ') {
						if (!ProxyConfigurationLoader.getInstance().getValue("username").equals(content)) {
							setError("Username is incorrect.\n" +
								"Remember: monitor version username password\n");
							return;
						}
						protocolState = ProtocolState.PASS;
						content = "";
					} else {
						content = content + charBuffer[i];
					}
					break;
				case PASS:
					if (charBuffer[i] == '\n') {
						if (!ProxyConfigurationLoader.getInstance().getValue("password").equals(content)) {
							setError("Password is incorrect.\n" +
								"Remember: monitor version username password\n");
						}
						return;
					}
					content = content + charBuffer[i];
					break;
				default:
					break;
				}
			}
		}
	}

	@Override
	public void handleWrite(SelectionKey key) throws IOException {
		
		SocketChannel clntChan = (SocketChannel) key.channel();
		
		String output = getError();
		if (output == null) {
			output = monitor.toString();
		} else {
			output = "ERROR:\n" + output;
		}
		
		ByteBuffer buf;
		int len = output.length();
		int limit;
		
		for (int i = 0; i < len; i = i + bufSize) {
			limit = (len > i + bufSize) ? i + bufSize : len;
			buf = ByteBuffer.wrap((output.substring(i, limit) + "\n").getBytes());
			
			clntChan.write(buf);
		}
		
		clntChan.close();
		setError(null);
	}
	
	private String getError() {
		return error;
	}
}
